#include <algorithm>
#include <cassert>
#include <vector>

#include "Navigator.h"

enum class Direction {
  East,
  North,
  West,
  South,
};

struct Pose {
  uint32_t x, y;
  Direction d;
};

class Map {
public:
  Map(uint32_t width, uint32_t height, std::vector<bool> &&data)
      : width_{width}, height_{height}, data_{std::move(data)} {
    assert(data_.size() == width_ * height_);
  }

public:
  uint32_t width() const noexcept { return width_; }

  uint32_t height() const noexcept { return height_; }

  uint32_t area() const noexcept {
    return std::count(data_.begin(), data_.end(), false);
  }

  bool is_obstacle(uint32_t x, uint32_t y) const noexcept {
    assert(x + y * width_ < data_.size());

    return data_[x + y * width_];
  }

private:
  uint32_t width_;
  uint32_t height_;
  std::vector<bool> data_;
};

class Environment : public rock::Robot {
public:
  Environment(const Pose &pose, Map &&map)
      : pose_{pose}, map_{std::move(map)} {}

  bool move_forward() override {
    auto x = pose_.x;
    auto y = pose_.y;
    switch (pose_.d) {
    case Direction::East:
      if (x + 1 >= map_.width())
        return false;

      ++x;
      break;

    case Direction::West:
      if (x == 0)
        return false;

      --x;
      break;

    case Direction::North:
      if (y == 0)
        return false;

      --y;
      break;

    case Direction::South:
      if (y + 1 >= map_.height())
        return false;

      ++y;
      break;

    default:
      return false;
    }

    if (map_.is_obstacle(x, y))
      return false;

    pose_.x = x;
    pose_.y = y;
    return true;
  }

  void turn_left() override {
    switch (pose_.d) {
    case Direction::East:
      pose_.d = Direction::North;
      break;

    case Direction::North:
      pose_.d = Direction::West;
      break;

    case Direction::West:
      pose_.d = Direction::South;
      break;

    case Direction::South:
      pose_.d = Direction::East;
      break;

    default:
      break;
    }
  }

  void turn_right() override {
    switch (pose_.d) {
    case Direction::East:
      pose_.d = Direction::South;
      break;

    case Direction::North:
      pose_.d = Direction::East;
      break;

    case Direction::West:
      pose_.d = Direction::North;
      break;

    case Direction::South:
      pose_.d = Direction::West;
      break;

    default:
      break;
    }
  }

  uint32_t area() const { return map_.area(); }

  auto pose() const -> const Pose & { return pose_; }

private:
  Pose pose_;
  Map map_;
};

int main() {
  using namespace rock;

  std::vector<bool> data = {
#include "map_data"
  };

  Map map{10, 10, std::move(data)};
  Pose pose{2, 2, Direction::East};

  Environment env{pose, std::move(map)};

  Navigator navigator;

  assert(env.area() == navigator.compute_area(env));

  return 0;
}